package sbt.metamodel;

import java.util.Map;

import sbt.metamodel.TraceListener.ChangeType;

/**
 * I provide an abstract base class for method transformations.
 * <p>
 * Subclasses should use {@link #getTarget()} to get the target trace, and
 * override the following abstract methods.
 * 
 * @author pa
 *
 */
public abstract class TransformationMethod {
	
	private TransformedTrace target = null;
	
	/**
	 * Construct a TransformationMethod for a {@link TransformedTrace}.
	 * <p>
	 * <strong>Important</strong>: the constructed TransformationMethod will
	 * not be operational until its {@link #init(TransformedTrace)} method has
	 * been called. As a consequence:<ul>
	 * <li>it should normally <strong>not</strong> be overriden;
	 * instead, override {@link #init(TransformedTrace)}.</li>
	 * <li>it should <em>only</em> be used as the first argument of constructor
	 * {@link TransformedTrace#TransformedTrace(TransformationMethod, java.util.Set, Map)}
	 * which will therefore call {@link #init(TransformedTrace)}.</li>
	 * </ul>
	 */
	public TransformationMethod() {
	}
	
	/**
	 * Initializes this method.
	 * @param target
	 */
	protected void init(TransformedTrace target) {
		this.target = target;
		notifyMetadataChange();
	}
	
	/**
	 * The target trace of this method.
	 * <p>
	 * Will raise a {@link MetamodelException} if
	 * {@link #init(TransformedTrace)} has not been called.
	 * 
	 * @return the target trace
	 */
	protected TransformedTrace getTarget() {
		if (target == null) {
			throw new MetamodelException("Misued TransformationMethod");
		}
		return target;
	}
	
	/**
	 * Compute the origin of the target trace.
	 * @return the computed origin
	 */
	abstract protected String computeOrigin();

	/**
	 * Compute the model URI of the destination trace.
	 * @return the computed model URI
	 */
	abstract protected String computeModel();

	/**
	 * Update the obsels of the destination trace.
	 */
	abstract protected void updateObsels();
	
	/**
	 * Called whenever a source trace's obsels change.
	 * <p>
	 * Implements
	 * {@link TraceListener#notifyObselsChange(AbstractTrace, ChangeType)}

	 * @param source  a source trace
	 * @param type  the type of change
	 * 
	 * @see #hasPendingChanges()
	 */
	abstract protected void notifyObselsChange(AbstractTrace source,
			ChangeType type);

	/**
	 * Called whenever the sources or parameters of the target source change.
	 * <p>
	 * Implements
	 * {@link TraceListener#notifyObselsChange(AbstractTrace, ChangeType)}
     * <p>
	 * Should check the metadata (parameters and sources) of the target trace
	 * and raise an exception if they are not acceptable.
	 */
	abstract protected void notifyMetadataChange();

    /**
     * Must this method still process pending changes to the source traces?
     * @return whether this method has changes to process
     */
	abstract protected boolean hasPendingChanges();


	
	/**
	 * Clear the target trace obsels.
	 */
	protected void clearTargetObsels() {
		getTarget().clearObsels();
	}
	
	/**
	 * Add an obsel into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin, long end,
    		String subject, Map<String, Object> attributes,
    		Map<String, Obsel> related, Map<String, Obsel> relating) {
    	return getTarget().createObsel(type, begin, end, subject, attributes,
    			related, relating);
    }    

	/**
	 * Add an obsel without incoming relation into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin, long end,
    		String subject, Map<String, Object> attributes,
    		Map<String, Obsel> related) {
    	return getTarget().createObsel(type, begin, end, subject, attributes,
    			related, null);
    }    

	/**
	 * Add an obsel without relation into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin, long end,
    		String subject, Map<String, Object> attributes) {
    	return getTarget().createObsel(type, begin, end, subject, attributes,
    			null, null);
    }    

	/**
	 * Add an obsel without attribute nor relation into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin, long end,
    		String subject) {
    	return getTarget().createObsel(type, begin, end, subject, null,
    			null, null);
    }    

    
    
    
	/**
	 * Add an instant obsel into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin,
    		String subject, Map<String, Object> attributes,
    		Map<String, Obsel> related, Map<String, Obsel> relating) {
    	return getTarget().createObsel(type, begin, begin, subject, attributes,
    			related, relating);
    }    

	/**
	 * Add an insant obsel without incoming relation into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin,
    		String subject, Map<String, Object> attributes,
    		Map<String, Obsel> related) {
    	return getTarget().createObsel(type, begin, begin, subject, attributes,
    			related, null);
    }    

	/**
	 * Add an instant obsel without relation into the target trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin,
    		String subject, Map<String, Object> attributes) {
    	return getTarget().createObsel(type, begin, begin, subject, attributes,
    			null, null);
    }    

	/**
	 * Add an instant obsel without attribute nor relation into the target
	 * trace.
	 * @see AbstractTrace#createObsel(String, long, long, String, Map, Map, Map)
	 */
    protected Obsel createObsel(String type, long begin,
    		String subject) {
    	return getTarget().createObsel(type, begin, begin, subject, null,
    			null, null);
    }    

}
